//ECE 454 Spring 2008 Assignment 1
//Macaranas, Joseph jjmacara
//Turner, Travis t2turner
#include "marshal.h"

//used to copy output args into proper locations
int copyArgs(int *argTypes, void **src, void **dst);

//client stub's cache mapping functions to servers, like in binder, but data only from previous rpc calls
std::map<std::string, server_list> cli_cache;

int rpcCall(char *name, int *argTypes, void **args) {
	cout << "Client has requested for a remote procedure call on function " << name << "." << endl;
	//check cli_cache if there is a server_address for the function
	map<string, server_list>::iterator temp = cli_cache.find((string) name);
	//variables used to perform the remote procedure calls on servers
	function *functions = (function *)malloc(MAX_LENGTH);
	unsigned long *servers = (unsigned long*)malloc(MAX_LENGTH);
	//flag indicating if match is found in the cache
	bool match_made = false;
	bool found_in_cache = false;
    //found at least one server with matching name, compare argtypes
   	if (temp != cli_cache.end()) {
		//pointer to navigate the linked list in the mapping
   		server_list *curr_list;
   		curr_list = &temp->second;
		//traverse the linked list
   		while (curr_list != 0) {
   	   		//assume that arguments match, loop will check if assumption is correct
   	   		bool args_match = true;
			//check each argument type for the registering function compared to the current node in linked list
   	   		int *x = curr_list->argTypes;
   	   		int *y = argTypes;
   	   		//ensure lengths of arrays match
   	   		//ensure each element in array matches
   	   		while (1) {
   	   			//array length doesn't match
   	   			if (*x == 0 && *y != 0) {
   	   				args_match = false;
   	   				break;
   	   			//array length doesn't match
   	   			} else if (*y == 0 && *x != 0) {
   	   				args_match = false;
   	   				break;
   	   			//end of both arrays, true match
   	   			} else if (*x == 0 && *y == 0) {
   	   				break;
   	   			//current element in both arrays match
   	   			//check next element
   	   			} else {
   	   				x++;
   	   				y++;
   	   			}
   	   		}
   	   		//all arguments matched
   	   		if (args_match) {
   	   			//retrieve server information from the cache
   	   			functions = curr_list->functions;
   	   			servers = curr_list->servers;
   	   			cout << "Servers that registered the function was previously saved." << endl;
   	   			match_made = true;
   	   			found_in_cache = true;
   	   			break;
   	   		//arguments did not match, check next node in linked list
   	   		} else {
   	   			curr_list = curr_list->next;
   	   		}
   	   	} //end while
	} //end if

	//match not found in cache, get a server from the binder
	if (!match_made) {
		cout << "Client was unable to find servers for the RPC request in the local cache." << endl;
		//obtain the machine name and port of the binder
		//label used for goto, when client should look at binder since cache failed
		GO_BACK: char *binder_addr = (char *) malloc(255);
		binder_addr = getenv("BINDER_ADDRESS");
		char *binder_port = (char *) malloc(5);
		binder_port = getenv("BINDER_PORT");
		if (binder_addr == NULL || binder_port == NULL) {
			//binder environment variables not set
			cerr << "Binder environment variables are not set." << endl;
			return NO_BINDER;
		}

		//set up connection to the binder
		struct  hostent *hb;
	   	struct  sockaddr_in binder_address;
	   	struct	sockaddr_in client_addr_b;
	   	int     sdb, sd2b;
		int     portb;
		int     nb;
		int		alenb;

		binder_address.sin_family = AF_INET;

		portb = atoi(binder_port);
		binder_address.sin_port = htons((u_short)portb);

		//convert the machine name of the binder to an ip address
		hb = gethostbyname(binder_addr);
		if (((char *)hb) == NULL) {
			cerr << "Invalid host name for binder." << endl;
			cerr << "Reason: " << strerror(errno) << endl;
			return INVALID_HOST;
		}
		memcpy(&binder_address.sin_addr, hb->h_addr, hb->h_length);

		sdb = socket(PF_INET, SOCK_DGRAM, 0);

		if (sdb < 0) {
			cerr << "Socket creation to send a request to the binder failed." << endl;
			cerr << "Reason: " << strerror(errno) << endl;
			return SOCKET_FAILURE;
		}

		binder_msg *send_this = (binder_msg *)malloc(MAX_LENGTH);
		void* msg_to_binder = malloc(MAX_LENGTH);
		int msg_length = 0;

		send_this->requestType = CLIENT_LOOKUP;
		send_this->name = name;
		send_this->argTypes = argTypes;

		cout << "Binder sending lookup request to binder." << endl;

		//marshall the send_this binder_msg into a void *
		binderPackData(msg_to_binder, send_this, &msg_length);

		//send marshalled message to binder
		nb = sendto(sdb, msg_to_binder, msg_length, 0, (struct sockaddr *)&binder_address, sizeof(struct sockaddr) );
		if (nb == -1) {
			cerr << "Unable to transmit data to the binder." << endl;
			cerr << "Reason: " << strerror(errno) << endl;
			return SEND_ERROR;
		}

		cout << "Client successfully sent lookup request to binder." << endl;

		free(msg_to_binder);
		free(send_this);

		close(sdb);

		//setup connection to receive response from binder
		client_addr_b.sin_port = htons((u_short)portb + 1);
		client_addr_b.sin_family = AF_INET;
		client_addr_b.sin_addr.s_addr = INADDR_ANY;

        /* Create a socket */
        sd2b = socket (PF_INET, SOCK_DGRAM, 0);
        if (sd2b < 0) {
        	cerr << "Socket creation to receive a response failed." << endl;
        	cerr << "Reason: " << strerror(errno) << endl;
    		return SOCKET_FAILURE;
        }

        /* Bind a local address to the socket */
        if (bind(sd2b, (struct sockaddr *) &client_addr_b, sizeof(client_addr_b)) < 0) {
        	cerr << "Binding socket to receive a response failed." << endl;
        	cerr << "Reason: " << strerror(errno) << endl;
        	return SOCKET_FAILURE;
        }

        alenb = sizeof(struct sockaddr);

        //variables involving the received message from binder
        binder_msg *recv_this = (binder_msg *)malloc(MAX_LENGTH);
        void* msg_from_binder = malloc(MAX_LENGTH);
        
        cout << "Client waiting for response from binder for lookup request." << endl;

		//(blocking) receive response from binder
       	if ((nb = recvfrom(sd2b, msg_from_binder, MAX_LENGTH, 0, (struct sockaddr *)&client_addr_b, (socklen_t *)&alenb)) < 0) {
	       	cerr << "Unable to receive a response from the binder." << endl;
	       	cerr << "Reason: " << strerror(errno) << endl;
        	return RECV_ERROR;
		} else {
			cout << "Response received from binder, deciphering response..." << endl;
		}

		//demarshall response message
		binderUnpackData(msg_from_binder, recv_this);

		free(msg_from_binder);

		//add obtained server information (if not null) to cache
		//also add function and server information for current rpc call
		if (recv_this->requestType == REQUEST_SUCCESS) {
			server_list add_this;
			int func_size = 0;
			int serv_size = 0;
			add_this.functions = (function *)malloc(MAX_LENGTH);
			add_this.servers = (unsigned long *)malloc(MAX_LENGTH);
			function *funcPtr1 = functions;
			function *funcPtr2 = recv_this->functions;
			function *funcPtr3 = add_this.functions;
			unsigned long *servPtr1 = servers;
			unsigned long *servPtr2 = recv_this->servers;
			unsigned long *servPtr3 = add_this.servers;
			while (*funcPtr2 != 0) {
				*funcPtr1 = *funcPtr2;
				*funcPtr3 = *funcPtr2;
				funcPtr1++;
				funcPtr2++;
				funcPtr3++;
				func_size += sizeof(function);
			}
			*funcPtr1 = 0;
			*funcPtr3 = 0;
			funcPtr1++;
			funcPtr3++;
			func_size += sizeof(function);
			while (*servPtr2 != 0) {
				*servPtr1 = *servPtr2;
				*servPtr3 = *servPtr2;
				servPtr1++;
				servPtr2++;
				servPtr3++;
				serv_size += sizeof(unsigned long);
			}
			*servPtr1 = 0;
			*servPtr3 = 0;
			servPtr1++;
			servPtr3++;
			serv_size += sizeof(unsigned long);
			functions = (function *)realloc( functions, func_size );
			servers = (unsigned long *)realloc( servers, serv_size );
			add_this.functions = (function *)realloc( add_this.functions, func_size );
			add_this.servers = (unsigned long *)realloc( add_this.servers, serv_size);
			add_this.argTypes = argTypes;
			add_this.next = 0;
			//find place where to add the new linked list node in cache
			if (found_in_cache) {
				//only possible from goto
				//entry for this set of argTypes is in mapping
				map<string, server_list>::iterator itr = cli_cache.find((string) name);
				server_list *locator;
				locator = &itr->second;
				//indicates if correct node was found
				bool founder = false;
				//find node in linked list where argTypes match
				while (locator != 0) {
					int *typeCheck1 = locator->argTypes;
					int *typeCheck2 = argTypes;
					bool argsMatch = true;
					//check for matching argTypes
					while (1) {
   	   					//array length doesn't match
   	   					if (*typeCheck1 == 0 && *typeCheck2 != 0) {
   	   						argsMatch = false;
   	   						break;
   	   					//array length doesn't match
   	   					} else if (*typeCheck2 == 0 && *typeCheck1 != 0) {
   	   						argsMatch = false;
   	   						break;
   	   					//end of both arrays, true match
   	   					} else if (*typeCheck2 == 0 && *typeCheck2 == 0) {
   	   						break;
   	   					//current element in both arrays match
   	   					//check next element
   	   					} else {
   	   						typeCheck1++;
   	   						typeCheck2++;
   	   					}
   	   				}
   	   				//all arguments matched, place to add registering server in mapping has been found
   	   				if (argsMatch) {
   	   					founder = true;
   	   					break;
   	   				}
					locator = locator->next;	
				}
				//locator points to correct node, replace servers and functions in node
				//with the update from binder
				locator->functions = (function *)realloc(locator->functions, func_size);
				locator->servers = (unsigned long *)realloc(locator->servers, serv_size);
				//copy functions and servers
				function *aF = locator->functions;
				function *bF = add_this.functions;
				unsigned long *aS = locator->servers;
				unsigned long *bS = add_this.servers;
				while (*bS != 0) {
					*aS = *bS;
					*aF = *bF;
					aS++;
					bS++;
					aF++;
					bF++;
				} 
				//add in the 0s at the end
				*aS = 0;
				*aF = 0;
				//free memory
				free(add_this.functions);
				free(add_this.servers);
			//check to see if linked list for name exists in map, argTypes won't match
			} else {
				map<string, server_list>::iterator it = cli_cache.find((string) name);
				server_list *loc;
				loc = &it->second;
				//name exists in map, add node to end of linked list
				if (it != cli_cache.end()) {
					while (loc->next != 0) {
						loc = loc->next;
					}
					//at end of linked list
					loc->next = &add_this;
				//not in map, add to name to map
				} else {
					cli_cache.insert( pair<string, server_list>((string) name, add_this) );
				}
			}
			if (found_in_cache) {
				found_in_cache = false;
				//ensure binder not asked twice for server updates if all fail
			}
			free(recv_this->servers);
			free(recv_this->functions);
			free(recv_this);
			cout << "Servers that registered procedure were found." << endl;
			cout << "Servers added to local cache for future calls of the same procedure." << endl;
		} else { //REQUEST_ERROR returned
			//if no server found, return negative number
			free(recv_this->servers);
			free(recv_this->functions);
			free(recv_this);
			//below case only occurs if client cache had outdated information
			//and the servers in the client ended up being deregistered
			//remove entry in cache for function name with matching argTypes
			//FLAW: removes all cache entries for function name
			if (found_in_cache) {
				map<string, server_list>::iterator itit = cli_cache.find((string) name);
				if (itit != cli_cache.end()) {
					cli_cache.erase(itit);
				}
			}
			cerr << "No server implementing the procedure was registered with the binder." << endl;
			return NO_SERVER_IMPL;
		}
	}

	cout << endl << "Attempting to establish connections to servers that registered procedure." << endl;

	//loop through server addresses
	unsigned long *server;
	function *func;
	server = servers;
	func = functions;

	//go through each server address
	while (*server != 0) {

		//set up server connection for transmitting data
	   	struct  sockaddr_in server_address;
	   	struct	sockaddr_in client_addr;
	   	int     sd, sd2b;
		int     port;
		int     n;
		int		alen;

		server_address.sin_family = AF_INET;

		server_address.sin_port = htons((u_short)PORT);
		server_address.sin_addr.s_addr = *server;

		//exceptions are thrown when an exception has occured
		//during the current attempt to send a message to a server
		//this allows the client to ask the next server
		try {

			if (server_address.sin_addr.s_addr == 0) {
				//end this iteration of the loop
				throw REQUEST_ERROR;
			} else {
				cout << "Attempting to establish connection to server with ip address ";
				cout << *server << "." << endl;
			}

			sd = socket(PF_INET, SOCK_DGRAM, 0);
			if (sd < 0) {
				cerr << "Socket creation for sending RPC request to server failed. ";
				cerr << "Reason: " << strerror(errno) << endl;
				//end this iteration of the loop
				close(sd);
				throw SOCKET_FAILURE;
			}
			void *msg_to_server = (void *) malloc(MAX_LENGTH);
			int msgLength;

			//marshall rpc call parameters
			rpcPackData(argTypes, args, msg_to_server, &msgLength, func);
			msg_to_server = realloc(msg_to_server, msgLength);

			//send message to server
			n = sendto(sd, msg_to_server, msgLength, 0, (struct sockaddr *)&server_address, sizeof(struct sockaddr) );
			if (n == -1) {
				cerr << "Unable to transmit data to server." << endl;
				cerr << "Reason: " << strerror(errno) << endl;
				//end this iteration of the loop
				close(sd);
				throw SEND_ERROR;
			} else {
				cout << "Client successfully sent a message with the size of ";
				cout << n << " bytes to server." << endl;
			}
			free(msg_to_server);
			close(sd);

			//set up connection for receiving data from the server
			client_addr.sin_port = htons((u_short)(PORT + 1));
			client_addr.sin_family = AF_INET;
			client_addr.sin_addr.s_addr = INADDR_ANY;
        	/* Create a socket */
        	sd = socket (PF_INET, SOCK_DGRAM, 0);
        	if (sd < 0) {
        		cerr << "Socket creation for receiving a response from the server failed. ";
				cerr << "Reason: " << strerror(errno) << endl;
		    	//end this iteration of the loop
        		throw SOCKET_FAILURE;
        	}
        	/* Bind a local address to the socket */
        	if (bind(sd, (struct sockaddr *) &client_addr, sizeof(client_addr)) < 0) {
        		cerr << "Bind for receiving a response from the server failed. ";
       			cerr << "Reason: " << strerror(errno) << endl;
       		 	//end this iteration of the loop
        		throw SOCKET_FAILURE;
        	}
        	alen = sizeof(struct sockaddr);

        	//variables involving the received message from binder
        	thread_data *recv_this;
        	void* msg_from_server = (void *) malloc(MAX_LENGTH);

			//receive response from server
			cout << "Waiting for a response from the server..." << endl;
       		if ((n = recvfrom(sd, msg_from_server, MAX_LENGTH, 0, (struct sockaddr *)&client_addr, (socklen_t *)&alen)) < 0) {
	       		cerr << "Accept failed" << endl;
        		cerr << "Reason: " << strerror(errno) << endl;
        		//end this iteration of the loop
	       		throw RECV_ERROR;
			} else {
				cout << "Client successfully received a response from the server with ";
				cout << n << " bytes." << endl;
				cout << "Processing results..." << endl;
			}

			//demarshall response message
			recv_this = (thread_data *)malloc(MAX_LENGTH);
			rpcExtractData(msg_from_server, recv_this);
			free(msg_from_server);
			close(sd);

			cout << "Client is now copying values from output variables." << endl;

			//args = recv_this->args;
			copyArgs(argTypes, recv_this->args, args);

			free(recv_this);

			cout << "Client finished the remote procedure call." << endl << endl;

			//if success, return a value indicating this
			return 0;

		} catch(int e) {
       		//send message to next server, as the current one failed to return a successful result
			server++;
			func++;
		}
	}

	free(functions);
	free(servers);

	//end of loop means all servers failed
	cerr << "Remote procedure call attempt failed for all servers implementing the procedure." << endl;
	//check binder if all in cache failed
	if (found_in_cache) {
		cout << "Client requesting for cache update from binder." << endl;
		functions = (function *)malloc(MAX_LENGTH);
		servers = (unsigned long*)malloc(MAX_LENGTH);
		goto GO_BACK;
	}
	return ALL_SERV_FAIL;

}

int copyArgs(int *argTypes, void **src, void **dst) {

	int numParams = 0;

	for(; argTypes[numParams] != 0; numParams++) {
	}

	for(int j = 0; j < numParams; j++) {

		//the argument is an array in this case
		if ((argTypes[j] & 0xFFFF) > 1 && (argTypes[j] & 0x40000000) > 1) {

			int numElements = argTypes[j] & 0xFFFF;

			switch(argTypes[j] & 0xFF0000) {
			case rpcChar:
				for(int i = 0; i < numElements; i++) {
					((char *)dst[j])[i] = ((char *)src[j])[i]; //assign value of char to value pointed to by msgPtr
				}
			break;

			case rpcShort:
				for(int i = 0; i < numElements; i++) {
					((short *)dst[j])[i] = ((short *)src[j])[i]; //assign value of short to value pointed to by msgPtr
				}
			break;

			case rpcInt:
				for(int i = 0; i < numElements; i++) {
					//cout << "PACKING AN INT" << endl;
					((int *)dst[j])[i] = ((int *)src[j])[i]; //assign value of int to value pointed to by msgPtr
				}
			break;

			case rpcLong:
				for(int i = 0; i < numElements; i++) {
					//cout << "PACKING A Long" << endl;
					((long *)dst[j])[i] = ((long *)src[j])[i]; //assign value of long to value pointed to by msgPtr
				}
			break;

			case rpcFloat:
				for(int i = 0; i < numElements; i++) {
					//cout << "PACKING A FLOAT" << endl;
					((float *)dst[j])[i] = ((float *)src[j])[i]; //assign value of float to value pointed to by msgPtr
				}
			break;

			case rpcDouble:
				for(int i = 0; i < numElements; i++) {
					//cout << "PACKING A DOUBLE" << endl;
					((double *)dst[j])[i] = ((double *)src[j])[i]; //assign value of double to value pointed to by msgPtr
				}
			break;

			case rpcString:
				for(int i = 0; i < numElements; i++) {


					char *stringPtr = ((char **)src[j])[i];
					int LL = 0;
					while (*stringPtr != '\0') {
						stringPtr++;
						LL++;
					}

					cout << LL << endl;

					((char **)dst[j])[i] = (char *) malloc(LL * sizeof(char));

					cout << "allocated" << endl;

					int k = 0;
					for (; k < LL; k++) {
						//cout << ((char *)dst[j])[i] << endl;
						((char **)dst[j])[k] = ((char **)src[j])[k];
					}
					((char **)dst[j])[k] = '\0';

				}
			break;

			default:
				cerr << "Unknown parameter type!  GTFO" << std::endl;
				return RESULT_FAILURE;
			break;
			} //end switch
		} else if ((argTypes[j] & 0x40000000) > 1) {	//single argument case

			switch(argTypes[j] & 0xFF0000) {
			case rpcChar:
			{
				*(char *)dst[j] = *(char *)src[j]; //assign value of char to value pointed to by msgPtr
			}
			break;

			case rpcShort:
			{
				*(short *)dst[j] = *(short *)src[j]; //assign value of short to value pointed to by msgPtr
			}
			break;

			case rpcInt:
			{
				*(int *)dst[j] = *(int *)src[j]; //assign value of int to value pointed to by msgPtr
			}
			break;
			case rpcLong:
			{
				*(long *)dst[j] = *(long *)src[j]; //assign value of long to value polonged to by msgPtr
			}
			break;

			case rpcFloat:
			{
				*(float *)dst[j] = *(float *)src[j]; //assign value of float to value pofloated to by msgPtr
			}
			break;

			case rpcDouble:
			{
				*(double *)dst[j] = *(double *)src[j]; //assign value of double to value podoubleed to by msgPtr
			}
			break;

			case rpcString:
			{
				char *stringPtrS = (char *)src[j];
				int LL = 0;
				while (*stringPtrS != '\0') {
					stringPtrS++;
					LL++;
				}
				dst[j] = (void *)realloc(dst[j], LL * sizeof(char));
				int i = 0;
				for (; i < LL; i++) {
					((char *)dst[j])[i] = ((char *)src[j])[i];
				}
				((char *)dst[j])[i] = '\0';

			}
			break;

			default:
				cerr << "Unknown parameter type!  GTFO" << std::endl;
				return RESULT_FAILURE;
			break;
			} //end switch
		}
	}

	return 0;

}

int rpcTerminate(void) {
	char *binderHost;
	binderHost = getenv("BINDER_ADDRESS");

	cout << "Initiating rpcTerminate sequence." << endl;

	if(binderHost == NULL) {		//ensure binder address is valid
		cout << "BINDER_ADDRESS environment variable not set." << endl;
		exit(INVALID_HOST);
	}

	int port = atoi(getenv("BINDER_PORT"));	//get the port

	struct  hostent  *h;
	struct  sockaddr_in binder_address;
	int     sd;
	int     n;
	binder_msg *send_this;
	void 	*message;
	int		msgLength = 0;
	int 	alen;

	memset((char *)&binder_address, 0, sizeof(binder_address));	//zero out the binder address
	binder_address.sin_family = AF_INET;
	binder_address.sin_port = htons((u_short)port);

	h = gethostbyname(binderHost);
	if(((char *)h) == NULL)	{		//bad host name
		cerr << "Binder has invalid host name: " <<  binderHost << "." << endl << endl;
		exit(INVALID_HOST);
	}

	memcpy(&binder_address.sin_addr, h->h_addr, h->h_length);	//copy address to binder_address

	//create a socket to send message to the binder
	sd = socket(PF_INET, SOCK_DGRAM, 0);
	if (sd < 0){
	   cerr << "Socket creation for sending a request to the binder failed. ";
	   cerr << "Reason: " << strerror(errno) << endl << endl;
	   exit(SOCKET_FAILURE);
	}

	//set up a binder msg struct to send the data
	send_this = (binder_msg *)malloc(MAX_LENGTH);

	send_this->requestType = TERMINATOR;
	send_this->name = (char *)malloc(sizeof(char) * 4);
	send_this->name[0] = '\0';
	send_this->argTypes = (int *)malloc(sizeof(int));
	send_this->argTypes[0] = 0;
	send_this->functions = (function *)malloc(sizeof(function));
	send_this->functions[0] = 0;
	send_this->servers = (unsigned long *)malloc(sizeof(function));
	send_this->servers[0] = 0;

	message = malloc(MAX_LENGTH);

	//marshall the data into a message
	binderPackData(message, send_this, &msgLength);
	free(send_this);

	cout << "Client sending termination request to binder." << endl;

	//send the message
	n = sendto(sd, message, msgLength, 0,
		(struct sockaddr *) &binder_address, sizeof(struct sockaddr) );

	//something bad happened
	if (n < 0) {
		cout << "Server failed to transmit data to the binder." << endl << endl;
		close(sd);
		free(message);
		return SEND_ERROR;
	}
	close(sd);
	free(message);
	cout << "Client succesfully sent termination request to binder." << endl;
	cout << "Client shutting down." << endl;

	return 0;
}
